// (c) Peek Traffic Croatia, 2003. Shared

using System;
using System.IO;
using System.Reflection;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;
using NUnit.Extensions;
using NUnit.Framework;

namespace System.Diagnostics {

	internal class TestError {
	
		public TestError(object test, Exception exception) {
			Test = test;
			Exception = exception;
		}
		
		public string TestName {
			get {
				string testName = Test.GetType().Name;
				testName = string.Format("{0}", testName);
				return testName;
			}
		}
		
		public object Test;
		public Exception Exception;
	}

    public class TestRunner {
    
        public TestRunner(Assembly executingAssembly, string defaultNamespace) {
			m_executingAssembly		= executingAssembly;
			m_defaultNamespace      = defaultNamespace;
			m_automaticMode			= false;
			m_errors				= new ArrayList();
			m_failures				= new ArrayList();
			m_breakOnError			= false;
			m_breakOnFailure		= false;
        }
        
		public void Run(string[] testCaseNames) {
		    Trace.Listeners.Add(new TextWriterTraceListener(Console.Out, "console"));
			Trace.AutoFlush = true;
			Trace.IndentSize = 2;
		    if (testCaseNames.Length == 0) {
		        throw new ApplicationException("Test type not specified!");
		    }
		    string typeNameMask = testCaseNames[0];
		    string methodNameMask = null;
		    if (testCaseNames.Length > 1) {
				methodNameMask = testCaseNames[1];
		    }
		    Type[] testCases = GetTestCaseTypes(testCaseNames[0]);
		    if (null == testCases) {
		        throw new ApplicationException("Invalid test class specified");
		    }
			foreach (Type testCaseType in testCases) {
				Debug.Assert(null != testCaseType);
				try {
					RunTestCase(testCaseType, methodNameMask);
				}
				catch (Exception exception) {
					m_errors.Add(new TestError(testCaseType, exception));
				}
			}
			Trace.WriteLine("Tests are finnished.");
			if (m_errors.Count > 0) {
				foreach (TestError testError in m_errors) {
					Trace.WriteLine(String.Format("Error=[{0}] Exception=[{1}]", testError.TestName, GetDeepMessage(testError.Exception)));
				}
				Trace.WriteLine(String.Format("Total Error Count = [{0}]", m_errors.Count));
			}
			if (m_failures.Count > 0) {
				foreach (TestError testError in m_failures) {
					Trace.WriteLine(String.Format("Failure=[{0}] Exception=[{1}]", testError.TestName, GetDeepMessage(testError.Exception)));
				}
				Trace.WriteLine(String.Format("Total Failure Count = [{0}]", m_failures.Count));
			}
			Trace.WriteLine("Press enter to exit ...");
		}

    	public void AddError(object test, Exception exception) {
            Trace.WriteLine("E");
            Trace.WriteLine("MESSAGE[" + GetDeepMessage(exception) + "]");
            Trace.WriteLine("STACK[" + GetDeepStack(exception) + "]");
            m_errors.Add(new TestError(test, exception));
            if (m_breakOnError) {
	            Debugger.Break();
	        }
		}

		public void AddFailure(object test, Exception exception) {
            Trace.WriteLine("F");
            Trace.WriteLine("MESSAGE[" + GetDeepMessage(exception) + "]");
            Trace.WriteLine("STACK[" + GetDeepStack(exception) + "]");
            m_failures.Add(new TestError(test, exception));
            if (m_breakOnFailure) {
				Debugger.Break();
			}
		}

		public void StartTest(object test) {
		}

        public void EndTest(object test) {
		}
		
		protected long RunTestCaseMethod(Type testCaseType, string methodName) {
			Debug.Assert(null != testCaseType);
			object testCaseInstance = testCaseType.GetConstructor(Type.EmptyTypes).Invoke(new object[0]);
			DateTime startTime = DateTime.Now;
			if (null != testCaseType.GetMethod("SetUp")) {
				testCaseType.GetMethod("SetUp").Invoke(testCaseInstance, new object[0]);
			}
			try {
				testCaseType.GetMethod(methodName).Invoke(testCaseInstance, new object[0]);
			}
			finally {
				if (null != testCaseType.GetMethod("TearDown")) {
					testCaseType.GetMethod("TearDown").Invoke(testCaseInstance, new object[0]);
				}
			}
			DateTime endTime = DateTime.Now;
			return (long)(endTime - startTime).TotalMilliseconds;
		}

		protected bool ShouldRunMethod(MethodInfo methodInfo, string methodNameMask) {
			if (!methodInfo.Name.StartsWith("Test") || !methodInfo.IsPublic) {
				return false;
			}
			if (null != methodNameMask && methodNameMask.Length > 0) {
				if (!Regex.IsMatch(methodInfo.Name, methodNameMask)) {
					return false;
				}
			}
			if (m_automaticMode) {
				if (methodInfo.GetCustomAttributes(typeof(IgnoreAttribute), true).Length > 0) {
					return false;
				}
			}
			return true;
        }

		protected void RunTestCase(Type testCaseType, string methodNameMask) {
			Trace.WriteLine("Starting test case [" + testCaseType.Name + "]");
            foreach (MethodInfo methodInfo in testCaseType.GetMethods()) {
				if (ShouldRunMethod(methodInfo, methodNameMask)) {
					Trace.Indent();
					try {
						Trace.WriteLine("Method [" + methodInfo.Name + "] started");
						long runTime = 0;
						Trace.Indent();
						try {
							runTime = RunTestCaseMethod(testCaseType, methodInfo.Name);
						}
						finally {
							Trace.Unindent();
							Trace.WriteLine("Method [" + methodInfo.Name + "] ended after " + runTime.ToString() + " ms");
						}
					}
					finally {
						Trace.Unindent();
					}
					Trace.WriteLine("");
                }
		    }
		}

		protected Type[] GetTestCaseTypes(string testCaseClassName) {
			Debug.Assert(null != testCaseClassName);
			ArrayList testCaseTypes = new ArrayList();
			IList classNames;
			m_automaticMode = (testCaseClassName == "*");
			if (m_automaticMode) {
				classNames = new ArrayList();
				Type[] types = m_executingAssembly.GetTypes();
				foreach (Type type in types) {
					if (type.GetCustomAttributes(typeof(TestFixtureAttribute), true).Length > 0) {
						classNames.Add(type.Name);
					}
				}
			}
			else {
				classNames = new ArrayList(testCaseClassName.Split(';'));
			}
			try {
				foreach (string className in classNames) {
					string runClassName = className;
					if (-1 == runClassName.IndexOf('.')) {
						runClassName = m_defaultNamespace + "." + runClassName;
					}
					Type testCaseType = m_executingAssembly.GetType(runClassName, true, true);
					Debug.Assert(null != testCaseType);
					testCaseTypes.Add(testCaseType);
				}
			}
			catch (Exception exception) {
				Trace.WriteLine(GetDeepMessage(exception));
				return null;
			}
		    return (Type[])testCaseTypes.ToArray(typeof(Type));
		}


		/// <summary>
		/// Returns concatenated messages from exception stack.
		/// </summary>
		protected string GetDeepMessage(Exception exception) {
			Debug.Assert(null != exception);
			
		    StringBuilder messageBuilder = new StringBuilder();
		    while (null != exception) {
		        if (messageBuilder.Length > 0) {
		            messageBuilder.Append(" -< ");
		        }
		        messageBuilder.Append(exception.GetType().Name);
		        messageBuilder.Append(':');
		        messageBuilder.Append(exception.Message);
		        exception = exception.InnerException;
		    }
		    return messageBuilder.ToString();
		}

		/// <summary>
		/// Returns concatenated stack trace.
		/// </summary>
		protected string GetDeepStack(Exception exception) {
			Debug.Assert(null != exception);

		    StringBuilder messageBuilder = new StringBuilder();
		    while (null != exception) {
		        if (messageBuilder.Length > 0) {
		            messageBuilder.Append(Environment.NewLine);
		        }
		        messageBuilder.Append(exception.StackTrace);
		        exception = exception.InnerException;
		    }
		    return messageBuilder.ToString();
		}

		private string m_defaultNamespace;
		private Assembly m_executingAssembly;		
		private bool m_automaticMode;
		private IList m_errors;
		private IList m_failures;
		private bool m_breakOnError;
		private bool m_breakOnFailure;
	}
}